五大常用设计模式

https://www.ibm.com/developerworks/cn/opensource/os-php-designptrns/
设计模式一书将设计模式引入软件社区,该书的作者是 Erich GammaRichard HelmRalph JohnsonJohn Vlissides Design(俗称 “四人帮”)。

所介绍的设计模式背后的核心概念非常简单。经过多年的软件开发实践,Gamma 等人发现了某些具有固定设计的模式,就像建筑师设计房子和建筑物一样,可以为浴室的位置或厨房的构造方式开发模板。使用这些模板或者说设计模式意味着可以更快地设计更好的建筑物。同样的概念也适用于软件。

设计模式不仅代表着更快开发健壮软件的有用方法,而且还提供了以友好的术语封装大型理念的方法。例如,您可以说您正在编写一个提供松散耦合的消息传递系统,也可以说你正在编写名称为观察者的模式

用较小的示例展示模式的价值是非常困难的。这往往有些大材小用的意味,因为模式实际上是在大型代码库中发挥作用的。本文不展示大型应用程序,所以您需要思索的是在您自己的大型应用程序中应用示例原理的方法不是本文演示的代码本身

这不是说您不应该在小应用程序中使用模式。很多良好的应用程序都以小应用程序为起点,逐渐发展到大型应用程序,所以没有理由不以此类扎实的编码实践为基础。

既然您已经了解了设计模式以及它们的有用之处,现在我们来看看 PHP V5 的五种常用模式。

UML图解析

工厂模式

最初在设计模式 一书中,许多设计模式都鼓励使用松散耦合。要理解这个概念,让我们最好谈一下许多开发人员从事大型系统的艰苦历程。在更改一个代码片段时,就会发生问题,系统其他部分,您曾认为完全不相关的部分中也有可能出现级联破坏。

该问题在于紧密耦合 。系统某个部分中的函数和类严重依赖于系统的其他部分中函数和类的行为和结构。您需要一组模式,使这些类能够相互通信,但不希望将它们紧密绑定在一起,以避免出现联锁。

在大型系统中,许多代码依赖于少数几个关键类。需要更改这些类时,可能会出现困难。

例如,假设您有一个从文件读取的User类。您希望将其更改为从数据库读取的其他类,但是,所有的代码都引用从文件读取的原始类。这时候,使用工厂模式会很方便。

工厂模式是一种类,它具有为您创建对象的某些方法。您可以使用工厂类创建对象,而不直接使用 new。这样,如果您想要更改所创建的对象类型,只需更改该工厂即可。使用该工厂的所有代码会自动更改。

UML图

interface IUser
{
    public function getName();
}

class User implements IUser
{
    public function __construct($id)
    {
    }

    public function getName()
    {
        return 'Jack';
    }
}

class UserFactory
{
    public static function Create($id)
    {
        return new User($id);
    }
}

$uo = UserFactory::Create(1);
echo $uo->getName(), PHP_EOL; // Jack

有一种工厂模式的变体使用工厂方法。类中的这些公共静态方法构造该类型的对象。如果创建此类型的对象非常重要,此方法非常有用。

例如,假设您需要先创建对象,然后设置许多属性。此版本的工厂模式会将该进程封装在单个位置中,这样,不用复制复杂的初始化代码,也不必将复制好的代码在在代码库中到处粘贴。

UML图

interface IUser
{
    public function getName();
}

class User implements IUser
{
    public function __construct($id)
    {
    }

    public static function Load($id)
    {
        return new User($id);
    }

    public static function Create()
    {
        return new User(null);
    }

    public function getName()
    {
        echo 'Jack', PHP_EOL;
    }
}

$uo = User::Load(1);
$uo->getName(); // Jack

单例模式

某些应用程序资源是独占的,因为有且只有一个此类型的资源。例如,通过数据库句柄到数据库的连接是独占的。您希望在应用程序中共享数据库句柄,因为在保持连接打开或关闭时,它是一种开销,在获取单个页面的过程中更是如此。
单例模式可以满足此要求。如果应用程序每次包含且仅包含一个对象,那么这个对象就是一个
单例(Singleton)。

UML图

class DatabaseConnection
{
    private $_handle = null;

    private static $_instance = null;

    private function __construct()
    {
        $this->_handle = mysqli_connect(
            '127.0.0.1', 'root', '123456', 'project', '3306'
        );
    }

    public static function _instance()
    {
        if $_instance == null {
            self::$_instance = new self;
        }
        return self::$_instance;
    }

    public function getHandle()
    {
        return $this->_handle;
    }
}

print_r_instance()->getHandle();
print_r_instance()->getHandle();
/*
Handle = Object id #3
Handle = Object id #3
*/

您可以使用全局变量存储数据库句柄,但是,该方法仅适用于较小的应用程序。在较大的应用程序中,应避免使用全局变量,并使用对象和方法访问资源。

观察者模式

观察者模式为您提供了避免组件之间紧密耦合的另一种方法。
一个对象通过添加一个方法(该方法允许另一个对象,即观察者注册自己)使本身变得可观察。
当可观察的对象更改时,它会将消息发送到已注册的观察者。
这些观察者使用该信息执行的操作与可观察的对象无关。
结果是对象可以相互对话,而不必了解原因。

UML图

// 被观察者接口
interface IObservable
{
    public function addObserver($observer);
}
// 观察者接口
interface IObserver
{
    public function onChanged($sender, $args);
}
// 被观察者类
class UserList implements IObservable
{
    private $_observers = [];
    // 添加观察者
    public function addObserver($observer)
    {
        $this->_observers [] = $observer;
    }
    // 通知
    public function addCustomer($name)
    {
        foreach ($this->_observers as $obs) {
            $obs->onChanged($this, $name);
        }
    }
}
// 观察者类
class UserListLogger implements IObserver
{
    public function onChanged($sender, $args)
    {
        echo $args, ' added to user list';
    }
}
$ul = new UserList();
$ul->addObserver(new UserListLogger());
$ul->addCustomer('Jack'); // Jack added to user list

此代码定义四个元素:两个接口和两个类
IObservable 接口定义可以被观察的对象,UserList 实现该接口,以便将本身注册为可观察。IObserver 列表定义要通过怎样的方法才能成为观察者,UserListLogger 实现 IObserver 接口。

认识到 UserList 不知道日志程序将执行什么操作很关键。可能存在一个或多个执行其他操作的侦听程序。例如,您可能有一个向新用户发送消息的观察者,欢迎新用户使用该系统。

这种方法的价值在于 UserList 忽略所有依赖它的对象,它主要关注在列表更改时维护用户列表并发送消息这一工作。

此模式不限于内存中的对象。它是在较大的应用程序中使用的数据库驱动的消息查询系统的基础。

职责链模式

命令链模式以松散耦合主题为基础,发送消息、命令和请求,或通过一组处理程序发送任意内容。每个处理程序都会自行判断自己能否处理请求。如果可以,该请求被处理,进程停止。您可以为系统添加或移除处理程序,而不影响其他处理程序。

UML图

// 命令接口
interface ICommand
{
    function onCommand($name, $args);
}
// 命令链
class CommandChain
{
    private $_commands = [];
    // 添加命令
    public function addCommand($cmd)
    {
        $this->_commands [] = $cmd;
    }
    // 运行命令
    public function runCommand($name, $args)
    {
        foreach ($this->_commands as $cmd) {
            if ($cmd->onCommand($name, $args)) return;
        }
    }
}
// 用户命令
class UserCommand implements ICommand
{
    public function onCommand($name, $args)
    {
        if ($name != 'addUser') return false;
        echo 'UserCommand handling "addUser"', PHP_EOL;
        return true;
    }
}
// 邮件命令
class MailCommand implements ICommand
{
    public function onCommand($name, $args)
    {
        if ($name != 'mail') return false;
        echo 'MailCommand handling "mail"', PHP_EOL;
        return true;
    }
}

$cc = new CommandChain();
$cc->addCommand(new UserCommand());
$cc->addCommand(new MailCommand());
// UserCommand handling "addUser"
$cc->runCommand('addUser', null);
// MailCommand handling "mail"
$cc->runCommand('mail', null);

此代码定义维护 ICommand 对象列表的 CommandChain 类。
两个类都可以实现 ICommand 接口

  1. 一个对邮件的请求作出响应
  2. 另一个对添加用户作出响应。

策略模式

在此模式中,算法是从复杂类提取的,因而可以方便地替换。
在复杂的示例中,这些部分都在同一个类中。通过使用策略模式,您可将排列部分放入另一个类中,以便更改页排列的方式,而不影响搜索引擎的其余代码。

UML图

// 策略接口
interface Strategy
{
    public function filter($record);
}
// 筛选策略
class FindAfterStrategy implements Strategy
{
    private $name = '';

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function filter($record)
    {
        return stripos($record, $this->name) !== false;
    }
}
// 随机策略
class RandomStrategy implements Strategy
{
    public function filter($record)
    {
        return rand(0, 1) >= 0.5;
    }
}
// 用户列表类
class UserList
{
    private $_list = [];

    public function __construct(array $names = [])
    {
        $this->_list = $names;
    }

    public function add($name)
    {
        $this->_list[] = $name;
    }

    public function find($filter)
    {
        $recs = [];
        foreach ($this->_list as $user) {
            if ($filter->filter($user)) $recs [] = $user;
        }
        return $recs;
    }
}
$userList = new UserList(['Andy', 'Jack', 'Lori', 'Megan']);
print_r($userList->find(new FindAfterStrategy('a')));
print_r($userList->find(new RandomStrategy()));
/*
Array
(
    [0] => Andy
    [1] => Jack
    [2] => Megan
)
Array
(
    [0] => Andy
    [1] => Jack
)
*/

策略模式非常适合复杂数据管理系统或数据处理系统,二者在数据筛选、搜索或处理的方式方面需要较高的灵活性。

结束语

本文介绍的仅仅是 PHP 应用程序中使用的几种最常见的设计模式。在设计模式 一书中演示了更多的设计模式。不要因架构的神秘性而放弃。模式是一种绝妙的理念,适用于任何编程语言、任何技能水平。